Skip to content

refactor(crypto): 换用 CNG DPAPI#2962

Open
LinQingYuu wants to merge 15 commits into
devfrom
refactor/cng-dpapi
Open

refactor(crypto): 换用 CNG DPAPI#2962
LinQingYuu wants to merge 15 commits into
devfrom
refactor/cng-dpapi

Conversation

@LinQingYuu

@LinQingYuu LinQingYuu commented May 31, 2026

Copy link
Copy Markdown
Member

将 DPAPI 改为 CNG DPAPI,并优化了代码结构

Resolved #2687

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新功能:

  • 新增一种加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

增强改进:

  • 将加密密钥标识符集中为可复用的常量,用作密钥保护中的熵值。
  • 引入 ReadOnlySpan<char> 扩展辅助方法,以获取可复用的 UTF-8 字节表示,用于加密相关代码中。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新特性:

  • 引入新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行密钥保护。

增强内容:

  • 统一并集中管理加密密钥标识符,将其作为常量用于密钥保护熵值。
  • 添加字符串 span 扩展辅助方法,用于获取 ReadOnlySpan<char> 值的 UTF-8 字节表示,以便在与加密相关的代码中复用。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新功能:

  • 新增一种加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

增强改进:

  • 将加密密钥标识符集中为可复用的常量,用作密钥保护中的熵值。
  • 引入 ReadOnlySpan<char> 扩展辅助方法,以获取可复用的 UTF-8 字节表示,用于加密相关代码中。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

增强内容:

  • 重构加密密钥持久化机制,使用 PlainToolkit CNG DPAPI,并引入新的密钥格式版本。
  • 保留对使用先前基于 DPAPI 实现保护的旧版密钥的解密支持。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新功能:

  • 新增一种加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

增强改进:

  • 将加密密钥标识符集中为可复用的常量,用作密钥保护中的熵值。
  • 引入 ReadOnlySpan<char> 扩展辅助方法,以获取可复用的 UTF-8 字节表示,用于加密相关代码中。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新特性:

  • 引入新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行密钥保护。

增强内容:

  • 统一并集中管理加密密钥标识符,将其作为常量用于密钥保护熵值。
  • 添加字符串 span 扩展辅助方法,用于获取 ReadOnlySpan<char> 值的 UTF-8 字节表示,以便在与加密相关的代码中复用。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

新功能:

  • 新增一种加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

增强改进:

  • 将加密密钥标识符集中为可复用的常量,用作密钥保护中的熵值。
  • 引入 ReadOnlySpan<char> 扩展辅助方法,以获取可复用的 UTF-8 字节表示,用于加密相关代码中。
Original summary in English

Summary by Sourcery

将加密密钥持久化方式切换为使用基于 CNG 的 DPAPI,同时保持与现有由 DPAPI 保护的密钥的兼容性。

New Features:

  • 添加一种新的加密密钥格式版本,使用 PlainToolkit CNG DPAPI 进行保护。

Enhancements:

  • 将加密密钥标识符集中为可重用的常量,用作密钥保护中的熵值。
  • 添加针对 stringReadOnlySpan<char> 的扩展辅助方法,以获取 UTF-8 字节表示,用于在加密相关代码中复用。
Original summary in English

Summary by Sourcery

Switch the encryption key persistence to use CNG-based DPAPI while preserving compatibility with existing DPAPI-protected keys.

New Features:

  • Add a new encryption key format version protected with PlainToolkit CNG DPAPI.

Enhancements:

  • Centralize the encryption key identifier as a reusable constant for use as entropy in key protection.
  • Add string and ReadOnlySpan extension helpers to obtain UTF-8 byte representations for reuse in encryption-related code.

@LinQingYuu LinQingYuu requested a review from a team May 31, 2026 03:59
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: M PR 大小评估:中型 labels May 31, 2026
@sourcery-ai

sourcery-ai Bot commented May 31, 2026

Copy link
Copy Markdown

审阅者指南

重构了密钥持久化逻辑,使用 PlainToolkit CNG DPAPI 和新的密钥格式版本,同时保持对旧的 DPAPI 保护密钥的向后兼容;将加密熵密钥常量集中管理,并新增基于 span 的 UTF-8 编码辅助方法,以便在与加密相关的代码中复用。

使用 CNG DPAPI 的更新版加密密钥持久化时序图

sequenceDiagram
    participant EncryptHelper
    participant File as FileSystem
    participant EncryptionData
    participant ProtectedData
    participant CngProtectedData

    EncryptHelper->>File: File.Exists(keyFile)
    alt keyFile exists
        EncryptHelper->>File: File.ReadAllBytes(keyFile)
        EncryptHelper->>EncryptionData: FromBytes(buf)
        EncryptionData-->>EncryptHelper: EncryptionData data
        alt data.Version == 1
            EncryptHelper->>ProtectedData: Unprotect(data.Data, Key, DataProtectionScope.CurrentUser)
            ProtectedData-->>EncryptHelper: encryptionKey
        else data.Version == 2
            EncryptHelper->>CngProtectedData: Unprotect(data.Data, Key, CngDataProtectionScope.CurrentUser)
            CngProtectedData-->>EncryptHelper: encryptionKey
        end
    else keyFile missing
        EncryptHelper->>EncryptHelper: RandomNumberGenerator.Fill(randomKey)
        EncryptHelper->>CngProtectedData: Protect(randomKey, Key, CngDataProtectionScope.CurrentUser)
        CngProtectedData-->>EncryptHelper: protectedData
        EncryptHelper->>EncryptionData: ToBytes(new EncryptionData{ Version = 2, Data = protectedData })
        EncryptionData-->>EncryptHelper: storeData
        EncryptHelper->>File: new FileStream(tmpFile, Create, ReadWrite, None)
        EncryptHelper->>File: FileStream.Write(storeData)
        EncryptHelper->>File: FileStream.Flush(true)
        EncryptHelper->>File: File.Move(tmpFile, keyFile, true)
        EncryptHelper-->>EncryptHelper: encryptionKey = randomKey
    end
Loading

文件级变更

Change Details Files
将加密密钥持久化方式从传统 DPAPI 切换为带版本的 CNG DPAPI,并保持向后兼容。
  • 引入可复用的 UTF-8 字节密钥常量,从字符串标识符派生,用作 DPAPI/CNG 的熵。
  • 更新密钥加载逻辑,根据存储的元数据,支持版本 1(System.Security.Cryptography.ProtectedData)和版本 2(PlainToolkit.CngProtectedData)的解密。
  • 变更新密钥生成及持久化逻辑,始终以版本 2 的 CNG 保护方式存储,同时保留读取现有版本 1 密钥的能力。
  • 通过移除 else 分支嵌套来简化密钥文件创建流程,同时仍通过临时文件和 File.Move 保证原子写入。
PCL.Core/Utils/Secret/EncryptHelper.cs
添加 string 和 ReadOnlySpan 辅助方法,用于获取在加密相关路径中复用的 UTF-8 字节。
  • 添加 StringExtension 扩展方法,将封装的字符串转换为 byte[],默认使用 UTF-8,也可指定 Encoding。
  • 添加 ReadOnlySpan 扩展方法,将内容编码到提供的 Span 中并返回写入的字节数,默认使用 UTF-8 编码。
  • 为新的扩展方法添加所需的 System.Runtime.CompilerServices 和 System.Text using 引用。
PCL.Core/Utils/Exts/StringExtension.cs

针对关联 issue 的评估

Issue Objective Addressed Explanation
#2687 通过调整加密密钥保护机制(同时保留对现有已存储密钥的支持),修复在完成官方网页登录后数据加/解密间歇性失败的问题。
#2687 重构加密工具,使用集中、可复用的加密密钥标识符以及用于获取在加密相关代码中使用的 UTF-8 字节表示的辅助方法。

技巧与命令

与 Sourcery 交互

  • 触发新的代码审查: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 通过回复某条审查评论,要求 Sourcery 从该评论创建 issue。你也可以在审查评论下回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写上 @sourcery-ai,即可在任意时间生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 正文任意位置写上 @sourcery-ai summary,即可在任意时间在指定位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成审阅者指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve 来解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 一次性忽略所有 Sourcery 审查: 在 pull request 中评论 @sourcery-ai dismiss 来忽略所有现有的 Sourcery 审查。特别适用于你想从头开始一次新的审查时——别忘了再评论 @sourcery-ai review 来触发新的审查!

自定义你的体验

访问你的 控制面板 来:

  • 启用或禁用审查功能,例如 Sourcery 自动生成的 PR 摘要、审阅者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

Refactors the secret key persistence to use PlainToolkit CNG DPAPI with a new key format version while keeping backward compatibility with the old DPAPI-protected keys, centralizes the encryption entropy key constant, and adds span-based UTF-8 encoding helpers for reuse in crypto-related code.

Sequence diagram for updated encryption key persistence with CNG DPAPI

sequenceDiagram
    participant EncryptHelper
    participant File as FileSystem
    participant EncryptionData
    participant ProtectedData
    participant CngProtectedData

    EncryptHelper->>File: File.Exists(keyFile)
    alt keyFile exists
        EncryptHelper->>File: File.ReadAllBytes(keyFile)
        EncryptHelper->>EncryptionData: FromBytes(buf)
        EncryptionData-->>EncryptHelper: EncryptionData data
        alt data.Version == 1
            EncryptHelper->>ProtectedData: Unprotect(data.Data, Key, DataProtectionScope.CurrentUser)
            ProtectedData-->>EncryptHelper: encryptionKey
        else data.Version == 2
            EncryptHelper->>CngProtectedData: Unprotect(data.Data, Key, CngDataProtectionScope.CurrentUser)
            CngProtectedData-->>EncryptHelper: encryptionKey
        end
    else keyFile missing
        EncryptHelper->>EncryptHelper: RandomNumberGenerator.Fill(randomKey)
        EncryptHelper->>CngProtectedData: Protect(randomKey, Key, CngDataProtectionScope.CurrentUser)
        CngProtectedData-->>EncryptHelper: protectedData
        EncryptHelper->>EncryptionData: ToBytes(new EncryptionData{ Version = 2, Data = protectedData })
        EncryptionData-->>EncryptHelper: storeData
        EncryptHelper->>File: new FileStream(tmpFile, Create, ReadWrite, None)
        EncryptHelper->>File: FileStream.Write(storeData)
        EncryptHelper->>File: FileStream.Flush(true)
        EncryptHelper->>File: File.Move(tmpFile, keyFile, true)
        EncryptHelper-->>EncryptHelper: encryptionKey = randomKey
    end
Loading

File-Level Changes

Change Details Files
Switch encryption key persistence from legacy DPAPI to CNG DPAPI with versioned keys and backward compatibility.
  • Introduce a reusable UTF-8 byte key constant derived from the string identifier for use as DPAPI/CNG entropy.
  • Update key loading logic to support both version 1 (System.Security.Cryptography.ProtectedData) and version 2 (PlainToolkit.CngProtectedData) unprotection based on stored metadata.
  • Change new key generation and persistence to always store as version 2 using CNG protection, while retaining ability to read existing version 1 keys.
  • Simplify key file creation flow by removing the else-branch nesting while preserving atomic write via temp file and File.Move.
PCL.Core/Utils/Secret/EncryptHelper.cs
Add string and ReadOnlySpan helpers to obtain UTF-8 bytes for reuse in encryption-related paths.
  • Add a StringExtension extension method to convert wrapped strings to byte[] using UTF-8 by default or a provided Encoding.
  • Add a ReadOnlySpan extension that encodes into a provided Span and returns the number of bytes written, defaulting to UTF-8 encoding.
  • Include required System.Runtime.CompilerServices and System.Text usings for the new extension helpers.
PCL.Core/Utils/Exts/StringExtension.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#2687 Fix the intermittent failure to encrypt/decrypt data after completing official web login by adjusting the encryption key protection mechanism (while preserving support for existing stored keys).
#2687 Refactor the crypto utilities to use a centralized, reusable encryption key identifier and helper methods for obtaining UTF-8 byte representations used in encryption-related code.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些更高层次的反馈:

  • Key 的熵 span 是用一个硬编码长度(stackalloc byte[22])和内联逻辑重新创建的;建议把这段逻辑抽取成一个小的辅助方法,通过 Encoding.UTF8.GetByteCount(Key) 计算出精确的字节数,以避免“魔法数字”和潜在的截断/填充问题。
  • 现在新生成的密钥会以版本 2 存储并使用 CNG DPAPI,而旧密钥仍然是版本 1,你可能需要考虑一个内联迁移路径(例如在加载时将 v1 密钥重新保护为 v2),以避免长期持久化混合密钥格式。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The entropy span for `Key` is recreated with a hard-coded length (`stackalloc byte[22]`) and inline logic; consider extracting this into a small helper that derives the exact byte count from `Encoding.UTF8.GetByteCount(Key)` to avoid magic numbers and potential truncation/padding issues.
- Now that new keys are stored as version 2 with CNG DPAPI while old ones remain at version 1, you may want to consider an inline migration path (e.g., re-protecting v1 keys as v2 upon load) to avoid persisting mixed key formats over time.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="156-158" />
<code_context>
         {
             var buf = File.ReadAllBytes(keyFile);
             var data = EncryptionData.FromBytes(buf);
+            Span<byte> store = stackalloc byte[22];
+            Key.GetBytes(Encoding.UTF8, store);
+            var space = (ReadOnlySpan<byte>)store;
             return data.Version switch
             {
</code_context>
<issue_to_address>
**issue (bug_risk):** The stackalloc buffer length likely does not match the actual UTF-8 byte length of `Key`, which can break decryption for existing v1 keys.

Because the stackalloc span is 22 bytes, you’re passing 22 bytes (including an extra 0) as entropy to `ProtectedData.Unprotect`, while v1 keys were protected with a 21-byte `_IdentifyEntropy` buffer. DPAPI treats all entropy bytes as significant, so this off-by-one will prevent decryption of existing v1 keys. Please either use `Encoding.UTF8.GetBytes(Key)` as before, or size the span via `Encoding.UTF8.GetByteCount(Key)` and slice it to the actual number of bytes written from `Key.GetBytes`.
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="162" />
<code_context>
             {
-                1 => ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser),
+                1 => ProtectedData.Unprotect(data.Data.AsSpan(), DataProtectionScope.CurrentUser, space),
+                2 => CngProtectedData.Unprotect(data.Data.AsSpan(), CngDataProtectionScope.CurrentUser, space),
                 _ => throw new NotSupportedException("Unsupported key version")
             };
</code_context>
<issue_to_address>
**issue (bug_risk):** Unprotect for v2 uses additional entropy, but Protect for v2 does not, which can cause decryption failures.

For v2, the key is protected without entropy but unprotected with `space` as entropy. If `CngProtectedData` matches DPAPI semantics, the entropy passed to `Unprotect` must be identical to what was used in `Protect`, so adding entropy only at unprotect time will cause decryption to fail. Align the calls by either omitting entropy on unprotect or using the same entropy during protect.
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The entropy span for Key is recreated with a hard-coded length (stackalloc byte[22]) and inline logic; consider extracting this into a small helper that derives the exact byte count from Encoding.UTF8.GetByteCount(Key) to avoid magic numbers and potential truncation/padding issues.
  • Now that new keys are stored as version 2 with CNG DPAPI while old ones remain at version 1, you may want to consider an inline migration path (e.g., re-protecting v1 keys as v2 upon load) to avoid persisting mixed key formats over time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The entropy span for `Key` is recreated with a hard-coded length (`stackalloc byte[22]`) and inline logic; consider extracting this into a small helper that derives the exact byte count from `Encoding.UTF8.GetByteCount(Key)` to avoid magic numbers and potential truncation/padding issues.
- Now that new keys are stored as version 2 with CNG DPAPI while old ones remain at version 1, you may want to consider an inline migration path (e.g., re-protecting v1 keys as v2 upon load) to avoid persisting mixed key formats over time.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="156-158" />
<code_context>
         {
             var buf = File.ReadAllBytes(keyFile);
             var data = EncryptionData.FromBytes(buf);
+            Span<byte> store = stackalloc byte[22];
+            Key.GetBytes(Encoding.UTF8, store);
+            var space = (ReadOnlySpan<byte>)store;
             return data.Version switch
             {
</code_context>
<issue_to_address>
**issue (bug_risk):** The stackalloc buffer length likely does not match the actual UTF-8 byte length of `Key`, which can break decryption for existing v1 keys.

Because the stackalloc span is 22 bytes, you’re passing 22 bytes (including an extra 0) as entropy to `ProtectedData.Unprotect`, while v1 keys were protected with a 21-byte `_IdentifyEntropy` buffer. DPAPI treats all entropy bytes as significant, so this off-by-one will prevent decryption of existing v1 keys. Please either use `Encoding.UTF8.GetBytes(Key)` as before, or size the span via `Encoding.UTF8.GetByteCount(Key)` and slice it to the actual number of bytes written from `Key.GetBytes`.
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="162" />
<code_context>
             {
-                1 => ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser),
+                1 => ProtectedData.Unprotect(data.Data.AsSpan(), DataProtectionScope.CurrentUser, space),
+                2 => CngProtectedData.Unprotect(data.Data.AsSpan(), CngDataProtectionScope.CurrentUser, space),
                 _ => throw new NotSupportedException("Unsupported key version")
             };
</code_context>
<issue_to_address>
**issue (bug_risk):** Unprotect for v2 uses additional entropy, but Protect for v2 does not, which can cause decryption failures.

For v2, the key is protected without entropy but unprotected with `space` as entropy. If `CngProtectedData` matches DPAPI semantics, the entropy passed to `Unprotect` must be identical to what was used in `Protect`, so adding entropy only at unprotect time will cause decryption to fail. Align the calls by either omitting entropy on unprotect or using the same entropy during protect.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread PCL.Core/Utils/Secret/EncryptHelper.cs Outdated
Comment thread PCL.Core/Utils/Secret/EncryptHelper.cs Outdated
Comment thread PCL.Core/Utils/Secret/EncryptHelper.cs Outdated
@LinQingYuu LinQingYuu requested a review from tangge233 June 1, 2026 12:06
@whitecat346

Copy link
Copy Markdown
Member

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体层面的反馈:

  • 新增的 ReadOnlySpan<char>.GetBytes 扩展方法当前会调用 ToString(),这违背了使用 span 的初衷并引入了额外的分配;建议直接使用 Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>) 这些重载,以避免中间字符串,并让这个工具方法真正保持对 span 友好。
  • 鉴于 ReadOnlySpan<char>.GetBytes 扩展目前只在固定的 Key 常量上使用,你可以考虑直接内联 Encoding.UTF8.GetBytes(Key)(或使用一个静态缓存的 byte[]),这样可以减少间接调用,同时避免引入一个可能在其他地方被误用或误解的通用扩展方法。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- The new ReadOnlySpan<char>.GetBytes extension methods currently call ToString(), which defeats the purpose of using spans and adds an allocation; consider using the Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>) overloads directly to avoid intermediate strings and keep this utility truly span-friendly.
- Given the ReadOnlySpan<char>.GetBytes extension is only used for the fixed Key constant, you might simplify by inlining Encoding.UTF8.GetBytes(Key) (or a static cached byte[]), reducing indirection and avoiding introducing a general-purpose extension that may be misused or misunderstood elsewhere.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="159" />
<code_context>
             return data.Version switch
             {
-                1 => ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser),
+                1 => ProtectedData.Unprotect(data.Data, DataProtectionScope.CurrentUser, space),
+                2 => CngProtectedData.Unprotect(data.Data, CngDataProtectionScope.CurrentUser, space),
                 _ => throw new NotSupportedException("Unsupported key version")
</code_context>
<issue_to_address>
**issue (bug_risk):** ProtectedData.Unprotect arguments appear to be in the wrong order compared to the standard API.

The original call `ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser)` matches the API `(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope)`. The new call `ProtectedData.Unprotect(data.Data, DataProtectionScope.CurrentUser, space)` swaps `optionalEntropy` and `scope`, which will either fail to compile or change behavior. This should be `ProtectedData.Unprotect(data.Data, space, DataProtectionScope.CurrentUser)` instead.
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Exts/StringExtension.cs" line_range="296-300" />
<code_context>
+            return encode.GetBytes(str.ToString());
+        }
+
+        public void GetBytes(Encoding? encode, Span<byte> space)
+        {
+            encode ??= Encoding.UTF8;
+            encode.GetBytes(str, space);
+        }
+    }
</code_context>
<issue_to_address>
**suggestion:** Consider returning the number of bytes written from the span-based GetBytes overload.

This overload currently returns `void`, whereas `Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>)` returns an `int` with the number of bytes written. Returning that `int` here (and optionally renaming `space` to something like `destination`) would give callers a reliable way to know how many bytes in the span are valid.

```suggestion
        public int GetBytes(Encoding? encode = null, Span<byte> destination)
        {
            encode ??= Encoding.UTF8;
            return encode.GetBytes(str, destination);
        }
```
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The new ReadOnlySpan.GetBytes extension methods currently call ToString(), which defeats the purpose of using spans and adds an allocation; consider using the Encoding.GetBytes(ReadOnlySpan, Span) overloads directly to avoid intermediate strings and keep this utility truly span-friendly.
  • Given the ReadOnlySpan.GetBytes extension is only used for the fixed Key constant, you might simplify by inlining Encoding.UTF8.GetBytes(Key) (or a static cached byte[]), reducing indirection and avoiding introducing a general-purpose extension that may be misused or misunderstood elsewhere.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new ReadOnlySpan<char>.GetBytes extension methods currently call ToString(), which defeats the purpose of using spans and adds an allocation; consider using the Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>) overloads directly to avoid intermediate strings and keep this utility truly span-friendly.
- Given the ReadOnlySpan<char>.GetBytes extension is only used for the fixed Key constant, you might simplify by inlining Encoding.UTF8.GetBytes(Key) (or a static cached byte[]), reducing indirection and avoiding introducing a general-purpose extension that may be misused or misunderstood elsewhere.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="159" />
<code_context>
             return data.Version switch
             {
-                1 => ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser),
+                1 => ProtectedData.Unprotect(data.Data, DataProtectionScope.CurrentUser, space),
+                2 => CngProtectedData.Unprotect(data.Data, CngDataProtectionScope.CurrentUser, space),
                 _ => throw new NotSupportedException("Unsupported key version")
</code_context>
<issue_to_address>
**issue (bug_risk):** ProtectedData.Unprotect arguments appear to be in the wrong order compared to the standard API.

The original call `ProtectedData.Unprotect(data.Data, _IdentifyEntropy, DataProtectionScope.CurrentUser)` matches the API `(byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope)`. The new call `ProtectedData.Unprotect(data.Data, DataProtectionScope.CurrentUser, space)` swaps `optionalEntropy` and `scope`, which will either fail to compile or change behavior. This should be `ProtectedData.Unprotect(data.Data, space, DataProtectionScope.CurrentUser)` instead.
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Exts/StringExtension.cs" line_range="296-300" />
<code_context>
+            return encode.GetBytes(str.ToString());
+        }
+
+        public void GetBytes(Encoding? encode, Span<byte> space)
+        {
+            encode ??= Encoding.UTF8;
+            encode.GetBytes(str, space);
+        }
+    }
</code_context>
<issue_to_address>
**suggestion:** Consider returning the number of bytes written from the span-based GetBytes overload.

This overload currently returns `void`, whereas `Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>)` returns an `int` with the number of bytes written. Returning that `int` here (and optionally renaming `space` to something like `destination`) would give callers a reliable way to know how many bytes in the span are valid.

```suggestion
        public int GetBytes(Encoding? encode = null, Span<byte> destination)
        {
            encode ??= Encoding.UTF8;
            return encode.GetBytes(str, destination);
        }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread PCL.Core/Utils/Secret/EncryptHelper.cs Outdated
Comment thread PCL.Core/Utils/Exts/StringExtension.cs Outdated
LinQingYuu and others added 3 commits June 4, 2026 12:48
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Changed Key to a readonly byte array and updated usage in _GetKey method.
@LinQingYuu

Copy link
Copy Markdown
Member Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并给出了一些整体性反馈:

  • 新增的 Key 字段被声明为 readnoly,并使用 "PCL CE Encryption Key".GetBytes();按当前写法代码无法通过编译(readonly 拼写错误,而且 GetBytes 是定义在 ReadOnlySpan<char> 上的扩展方法),所以要么保留原来的 Encoding.UTF8.GetBytes,要么为 string 添加一个合适的扩展重载。
  • _GetKey 中,调用 CngProtectedData.Protect(randomKey, CngDataProtectionScope.CurrentUser, space) 时引用了未定义的变量 space;如果你是想传入熵(entropy)密钥,这里很可能应该是 Key(或者另一个显式参数)。
  • 新增的 ReadOnlySpan<char> 扩展方法 GetBytes(Encoding? encode = null) 调用了 str.ToString() 再去编码,这样会削弱使用 span 的意义;建议使用基于 span 的 Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>) 重载(或类似方式),以避免中间字符串分配。
面向 AI 代理的提示词
Please address the comments from this code review:

## Overall Comments
- The new `Key` field is declared as `readnoly` and uses `"PCL CE Encryption Key".GetBytes()`; this won’t compile as written (typo on `readonly` and `GetBytes` is defined as a `ReadOnlySpan<char>` extension), so either keep the original `Encoding.UTF8.GetBytes` or add a proper `string` extension overload.
- In `_GetKey`, the call `CngProtectedData.Protect(randomKey, CngDataProtectionScope.CurrentUser, space)` references an undefined `space` variable; if you intended to pass the entropy key, this should likely be `Key` (or another explicit parameter) instead.
- The new `ReadOnlySpan<char>` extension `GetBytes(Encoding? encode = null)` calls `str.ToString()` and then encodes it, which defeats the purpose of using spans; consider using the span-based `Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>)` overload (or similar) to avoid the intermediate string allocation.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="16-19" />
<code_context>

 public static class EncryptHelper
 {
+    private static readnoly byte[] Key = "PCL CE Encryption Key".GetBytes();
     public static (IEncryptionProvider Provider, uint Version) DefaultProvider => _DefaultProvider.Value;
     private static readonly Lazy<(IEncryptionProvider Provider, uint Version)> _DefaultProvider = new(_SelectBestEncryption);
</code_context>
<issue_to_address>
**issue (typo):** Typo in `readnoly` will prevent this field from compiling.

Use the `readonly` keyword here; as written, this line won’t compile and the `Key` field won’t be defined correctly.

```suggestion
public static class EncryptHelper
{
    private static readonly byte[] Key = "PCL CE Encryption Key".GetBytes();
    public static (IEncryptionProvider Provider, uint Version) DefaultProvider => _DefaultProvider.Value;
```
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Exts/StringExtension.cs" line_range="290-293" />
<code_context>
+
+    extension(ReadOnlySpan<char> str)
+    {
+        public byte[] GetBytes(Encoding? encode = null)
+        {
+            encode ??= Encoding.UTF8;
+            return encode.GetBytes(str.ToString());
+        }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid allocating an intermediate string when encoding a `ReadOnlySpan<char>`.

`str.ToString()` adds an unnecessary allocation. Where supported, prefer the `Encoding.GetBytes(ReadOnlySpan<char>)` overload for spans. If you need to support older frameworks, consider `GetByteCount` + a rented/pooled buffer to avoid extra allocations in hot paths.

Suggested implementation:

```csharp
        public byte[] GetBytes(Encoding? encode = null)
        {
            encode ??= Encoding.UTF8;
            return encode.GetBytes(str);
        }

```

If this project must target frameworks that do not support `Encoding.GetBytes(ReadOnlySpan<char>)` (e.g., .NET Framework or .NET Standard 2.0), you will need to replace the direct `encode.GetBytes(str)` call with a fallback that:
1. Uses `encode.GetByteCount(str)` to determine the byte count.
2. Allocates or rents a `byte[]` of the required size (e.g., via `ArrayPool<byte>.Shared.Rent` in hot paths).
3. Calls `encode.GetBytes(str, destinationSpan)` to fill the buffer.
4. Returns either the exact-sized array or the rented buffer (trimming/copying if necessary).
You can use `#if` conditionals around the span-based overload to keep compatibility with older targets if needed.
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的代码评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • The new Key field is declared as readnoly and uses "PCL CE Encryption Key".GetBytes(); this won’t compile as written (typo on readonly and GetBytes is defined as a ReadOnlySpan<char> extension), so either keep the original Encoding.UTF8.GetBytes or add a proper string extension overload.
  • In _GetKey, the call CngProtectedData.Protect(randomKey, CngDataProtectionScope.CurrentUser, space) references an undefined space variable; if you intended to pass the entropy key, this should likely be Key (or another explicit parameter) instead.
  • The new ReadOnlySpan<char> extension GetBytes(Encoding? encode = null) calls str.ToString() and then encodes it, which defeats the purpose of using spans; consider using the span-based Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>) overload (or similar) to avoid the intermediate string allocation.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Key` field is declared as `readnoly` and uses `"PCL CE Encryption Key".GetBytes()`; this won’t compile as written (typo on `readonly` and `GetBytes` is defined as a `ReadOnlySpan<char>` extension), so either keep the original `Encoding.UTF8.GetBytes` or add a proper `string` extension overload.
- In `_GetKey`, the call `CngProtectedData.Protect(randomKey, CngDataProtectionScope.CurrentUser, space)` references an undefined `space` variable; if you intended to pass the entropy key, this should likely be `Key` (or another explicit parameter) instead.
- The new `ReadOnlySpan<char>` extension `GetBytes(Encoding? encode = null)` calls `str.ToString()` and then encodes it, which defeats the purpose of using spans; consider using the span-based `Encoding.GetBytes(ReadOnlySpan<char>, Span<byte>)` overload (or similar) to avoid the intermediate string allocation.

## Individual Comments

### Comment 1
<location path="PCL.Core/Utils/Secret/EncryptHelper.cs" line_range="16-19" />
<code_context>

 public static class EncryptHelper
 {
+    private static readnoly byte[] Key = "PCL CE Encryption Key".GetBytes();
     public static (IEncryptionProvider Provider, uint Version) DefaultProvider => _DefaultProvider.Value;
     private static readonly Lazy<(IEncryptionProvider Provider, uint Version)> _DefaultProvider = new(_SelectBestEncryption);
</code_context>
<issue_to_address>
**issue (typo):** Typo in `readnoly` will prevent this field from compiling.

Use the `readonly` keyword here; as written, this line won’t compile and the `Key` field won’t be defined correctly.

```suggestion
public static class EncryptHelper
{
    private static readonly byte[] Key = "PCL CE Encryption Key".GetBytes();
    public static (IEncryptionProvider Provider, uint Version) DefaultProvider => _DefaultProvider.Value;
```
</issue_to_address>

### Comment 2
<location path="PCL.Core/Utils/Exts/StringExtension.cs" line_range="290-293" />
<code_context>
+
+    extension(ReadOnlySpan<char> str)
+    {
+        public byte[] GetBytes(Encoding? encode = null)
+        {
+            encode ??= Encoding.UTF8;
+            return encode.GetBytes(str.ToString());
+        }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid allocating an intermediate string when encoding a `ReadOnlySpan<char>`.

`str.ToString()` adds an unnecessary allocation. Where supported, prefer the `Encoding.GetBytes(ReadOnlySpan<char>)` overload for spans. If you need to support older frameworks, consider `GetByteCount` + a rented/pooled buffer to avoid extra allocations in hot paths.

Suggested implementation:

```csharp
        public byte[] GetBytes(Encoding? encode = null)
        {
            encode ??= Encoding.UTF8;
            return encode.GetBytes(str);
        }

```

If this project must target frameworks that do not support `Encoding.GetBytes(ReadOnlySpan<char>)` (e.g., .NET Framework or .NET Standard 2.0), you will need to replace the direct `encode.GetBytes(str)` call with a fallback that:
1. Uses `encode.GetByteCount(str)` to determine the byte count.
2. Allocates or rents a `byte[]` of the required size (e.g., via `ArrayPool<byte>.Shared.Rent` in hot paths).
3. Calls `encode.GetBytes(str, destinationSpan)` to fill the buffer.
4. Returns either the exact-sized array or the rented buffer (trimming/copying if necessary).
You can use `#if` conditionals around the span-based overload to keep compatibility with older targets if needed.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread PCL.Core/Utils/Secret/EncryptHelper.cs
Comment thread PCL.Core/Utils/Exts/StringExtension.cs Outdated
@LinQingYuu

Copy link
Copy Markdown
Member Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我在这里给出了一些整体性的反馈:

  • EncryptHelper 中新的 Key 熵值目前以可变的 byte[] 形式保存;建议将其暴露为 ReadOnlyMemory<byte>,或者通过属性返回一个新的 span,以避免对整个进程范围内的加密常量发生意外修改。
  • 新增的字符串/ReadOnlySpan<char> GetBytes 帮助方法,把加密代码依赖到了通用的字符串扩展上;如果这些方法主要是为加密场景准备的,建议考虑将它们移动到更底层或更偏向加密的工具类中,以保持更清晰的分层结构。
  • 对于 ReadOnlySpan<char>.GetBytes(Span<byte> destination, Encoding? encode = null) 这个扩展方法,可能需要在文档里说明或封装一下 GetByteCount/GetBytes 的使用模式,这样调用方就能更容易地确定所需缓冲区大小,避免在目标 span 过小时出现意料之外的 ArgumentException
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The new `Key` entropy value in `EncryptHelper` is stored as a mutable `byte[]`; consider exposing it as a `ReadOnlyMemory<byte>` or returning a new span from a property to avoid accidental mutation of a process-wide crypto constant.
- The new string/`ReadOnlySpan<char>` `GetBytes` helpers introduce a dependency from crypto code to the general string extensions; if these are meant primarily for crypto usage, consider moving them to a more low-level/crypto-focused helper to keep layering clearer.
- For the `ReadOnlySpan<char>.GetBytes(Span<byte> destination, Encoding? encode = null)` extension, it might be helpful to document or wrap the `GetByteCount`/`GetBytes` pattern so callers can easily determine the required buffer size and avoid unexpected `ArgumentException` when the destination span is too small.

Sourcery 对开源项目是免费的——如果你喜欢我们的评审,请考虑帮忙分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've left some high level feedback:

  • The new Key entropy value in EncryptHelper is stored as a mutable byte[]; consider exposing it as a ReadOnlyMemory<byte> or returning a new span from a property to avoid accidental mutation of a process-wide crypto constant.
  • The new string/ReadOnlySpan<char> GetBytes helpers introduce a dependency from crypto code to the general string extensions; if these are meant primarily for crypto usage, consider moving them to a more low-level/crypto-focused helper to keep layering clearer.
  • For the ReadOnlySpan<char>.GetBytes(Span<byte> destination, Encoding? encode = null) extension, it might be helpful to document or wrap the GetByteCount/GetBytes pattern so callers can easily determine the required buffer size and avoid unexpected ArgumentException when the destination span is too small.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `Key` entropy value in `EncryptHelper` is stored as a mutable `byte[]`; consider exposing it as a `ReadOnlyMemory<byte>` or returning a new span from a property to avoid accidental mutation of a process-wide crypto constant.
- The new string/`ReadOnlySpan<char>` `GetBytes` helpers introduce a dependency from crypto code to the general string extensions; if these are meant primarily for crypto usage, consider moving them to a more low-level/crypto-focused helper to keep layering clearer.
- For the `ReadOnlySpan<char>.GetBytes(Span<byte> destination, Encoding? encode = null)` extension, it might be helpful to document or wrap the `GetByteCount`/`GetBytes` pattern so callers can easily determine the required buffer size and avoid unexpected `ArgumentException` when the destination span is too small.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: M PR 大小评估:中型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

随机出现无法解密/加密数据

3 participants